using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using T.FAS.Runtime.Base.Entity;
using T.FAS.Runtime.DataAccess.Common;
using T.FAS.Runtime.DataAccess.Kernel;
using T.FAS.Runtime.DataAccess.SPI;
using T.FAS.Runtime.DataAccess.SQLDom;
using T.FAS.Runtime.Metadata.Entity.DataModel;
using T.ServiceComponent.Kernel.Service;

namespace T.FAS.Runtime.DataAccess.Engine
{
    class CDODataSaveService : ISaveService<CompositeDataObject>
    {
        public event BuildDataAccessActionContext BuildContext;
        public event ExecuteDataAccessAction Execute;
        private readonly IDatabase _db = DatabaseFactory.CreateDefaultDatabase();
        private readonly IQueryService<CompositeDataObject> _saveService = new CDODataQueryService();
        private readonly IDACLogService _localLogService = DACLogFactory.GetDACLogService();
        
        private readonly OpeType _opeType = OpeType.Create;

        public TResult SaveData(CompositeDataObject metadata, TRecord newRecord, Dictionary<string, object> exrParams = null)
        {
            RpcContext.Current.Increase();
            if (metadata == null)
                throw new FasException(DataAccessErrorCode.CompositeDataObject.NullValue, CommonStringClassEngine.DataModelService_MetaDataCannotBeNull);
            if (newRecord == null)
                throw new FasException(DataAccessErrorCode.CompositeDataObject.NullValue, CommonStringClassEngine.DataModelService_TRecordCannotBeNull);
            DateTime start = DateTime.Now;
            Exception ex = null;
            bool isNeedAuth = false;
            try
            {
                //构造鉴权上下文
                if (DACControlContextHelper.IsNeedAuthAndTryGetAuthResult(metadata, out List<ColumnPermissionView> columnPermissionResult))
                {
                    isNeedAuth = true;
                }

                //初始化DB事件
                //InitDBEvent();
                RegistSQLExeSuccessdEvent(metadata);

                //初始化插件
                InvokeContext plugInInvokeContext = PluginInit(metadata, newRecord, null);
                plugInInvokeContext.InitAuthControl(isNeedAuth, columnPermissionResult);

                //触发入口事件
                PluginExecute(InjectionPoint.Entry);

                //注册到线程上下文，包含时间戳，供变更日志以及Update语句使用等
                DACContextHelper.Regist(exrParams);

                //触发执行sql前事件
                //1、保存前事件需要比较变更日志，传入前端的TRecord，供状态机插件使用
                DataAccessPluginExtParam extParam = new DataAccessPluginExtParam { NewRecord = newRecord };

                //_logService.Write(new { info = "状态机插件执行前的TRecord内容", cdo = metadata?.Code, content = TRecordA.GetTRecordDebugInfos() });

                /***********************************BeforeExecuteSQL******************************************/
                PluginExecute(InjectionPoint.BeforeExecuteSQL, extParam);

                //新建时获取pkVal需要在状态机执行前事件全部执行完毕后获取，以兼容在状态机保存前事件给主键赋值的情况
                string pkVal = GetCDOTRecordPrimaryKeyValue(metadata, newRecord);

                //_logService.Write(new { info = "SQL执行前的TRecord内容", cdo = metadata?.Code, content = TRecordA.GetTRecordDebugInfos() });

                //构造执行SQL 并执行
                SqlExecuteControlContext controlContext = DACControlContextHelper.GetDefaultControlContext(exrParams, metadata);
                controlContext.InitAuthControl(isNeedAuth, columnPermissionResult);

                var sqls = SQLBuilderFactory.CreateCDOSQLBuilder().ParseInsertSql(metadata, newRecord, controlContext);
                ExecuteSQL(metadata, sqls, pkVal);

                //从DB中查询刚刚保存的单据，以获取到DB默认值
                var cdoQueryResult = _saveService.QueryData(metadata, pkVal, DACControlContextHelper.BuildDefultExtParams()) as TCommonResult<TRecord>;

                //将DB中查询出来的TRecord注入到插件内容，防止插件内部多次查询，提高效率，入参统一为Record类型
                var newQueryRecord = cdoQueryResult.Record;
                newQueryRecord.AllColumnInfos = newRecord.AllColumnInfos;
                DataAccessPluginExtParam pluginExtParam_AfterSQLExecuted = new DataAccessPluginExtParam { NewRecord = newQueryRecord };

                //克隆Record，返回前端的值应与从DB中取出的一致，不受状态机保存后事件影响
                var newRecordForReturn = GetCreatedData(metadata, newQueryRecord);
                PluginExecute(InjectionPoint.Finish, pluginExtParam_AfterSQLExecuted);

                //清空线程上下文注册的信息
                DACContextHelper.UnRegist();

                cdoQueryResult.Record = newRecordForReturn;
                return cdoQueryResult;
            }
            catch (Exception e)
            {
                var msg = $"CDO:{metadata.Code}执行Save异常，msg:{e.Message}";
                _localLogService.WriteError(e, msg);
                ex = new Exception(msg, e);
                CDOMonitor.RootCaseReport(metadata, start, ex, _opeType);
                throw ex;
            }
            finally
            {
                CDOMonitor.MonitorTPS(metadata, _opeType, ex);
                CDOMonitor.MonitorTrace(metadata, start, _opeType, ex, newRecord);
                CDOMonitor.MonitorDuration(metadata, (DateTime.Now - start).TotalMilliseconds, _opeType, ex);
            }
        }

        public TResult SaveData(CompositeDataObject metadata, string cnoId, TRecord newTRecordA, Dictionary<string, object> exrParams = null)
        {
            if (metadata == null)
                throw new FasException(DataAccessErrorCode.CompositeDataObject.NullValue, CommonStringClassEngine.DataModelService_MetaDataCannotBeNull);
            if (cnoId == null)
                throw new FasException(DataAccessErrorCode.CompositeDataObject.NullValue, CommonStringClassEngine.DataModelService_CNOIDCannotBeNull);
            if (newTRecordA == null)
                throw new FasException(DataAccessErrorCode.CompositeDataObject.NullValue, CommonStringClassEngine.DataModelService_TRecordCannotBeNull);
            //初始化DB事件
            //InitDBEvent();
            RegistSQLExeSuccessdEvent(metadata);

            //初始化插件
            PluginInit(metadata, newTRecordA, null);

            //触发入口事件
            PluginExecute(InjectionPoint.Entry);

            //注册时间戳，供变更日志以及Update语句使用
            DACContextHelper.Regist(exrParams);

            //_localLogService.Write(new { info = "状态机插件执行前的TRecord内容", cdo = metadata?.Code, content = NewTRecordA.GetTRecordDebugInfos() });
            //触发执行sql前事件
            PluginExecute(InjectionPoint.BeforeExecuteSQL);
            //_localLogService.Write(new { info = "SQL执行前的TRecord内容", cdo = metadata?.Code, content = NewTRecordA.GetTRecordDebugInfos() });

            var cno = metadata.CompositeNodeObjects.FirstOrDefault(i => i.ID == cnoId);
            if (cno == null)
            {
                throw new FasException(DataAccessErrorCode.CompositeDataObject.NullValue, CommonStringClassEngine.CDOService_CannotFindCNOInstance + cnoId);
            }

            var sqls = SQLBuilderFactory.CreateCDOSQLBuilder().ParseInsertSql(metadata, cno, newTRecordA);
            //执行SQL
            ExecuteSQL(metadata, sqls);
            var pkVal = GetCDOTRecordPrimaryKeyValue(metadata, newTRecordA);
            var result = _saveService.QueryData(metadata, pkVal, DACControlContextHelper.BuildDefultExtParams()) as TCommonResult<TRecord>;
            var NewTRecordB = result.Record;
            //触发sql执行成功后事件，save需要向插件内注入查询结果，提高效率，入参统一为Record类型
            DataAccessPluginExtParam pluginParam = new DataAccessPluginExtParam
            {
                NewRecord = NewTRecordB,
            };
            //克隆Record
            var newRecordC = GetCreatedData(metadata, NewTRecordB);
            PluginExecute(InjectionPoint.Finish, pluginParam);
            //清空线程上下文注册的信息
            DACContextHelper.UnRegist();

            result.Record = newRecordC;
            return result;
        }

        #region CDO插件执行相关
        private InvokeContext PluginInit(CompositeDataObject compositeDataObject, TRecord record, string cnoId = null, object extData = null)
        {
            var invokeContext = DataAccessPluginExecutor.BuildInvokeContext(DataModelConst.CDOMetaType, compositeDataObject, cnoId, record);
            this.BuildContext(invokeContext);
            return invokeContext;
        }

        //public void PluginExecute(CompositeDataObject compositeDataObject, TRecord record, InjectionPoint injectionPoint, string cnoId = null, object extData = null)
        //{
        //    /************统一数据访问插件执行点 Start************/
        //    try
        //    {
        //        var invokeContext = DataAccessPluginExecutor.BuildInvokeContext(DataModelConst.CDOMetaType, compositeDataObject, cnoId, record);
        //        var actionResult = this.Execute(InjectionMethodType.Create, injectionPoint, extData);
        //        DataAccessPluginExecutor.DefaultHandleActionResult(actionResult);
        //    }
        //    catch (Exception e)
        //    {
        //        throw new FasException(DataAccessErrorCode.PlugIn.Error, $"数据访问服务插件执行异常,Msg:{e.Message}", e);
        //    }
        //}

        private void PluginExecute(InjectionPoint injectionPoint, DataAccessPluginExtParam extData = null)
        {
            /************统一数据访问插件执行点 Start************/
            try
            {
                //var invokeContext = DataAccessPluginExecutor.BuildInvokeContext(DataModelConst.CDOMetaType, compositeDataObject, cnoId, record);
                var actionResult = this.Execute(InjectionMethodType.Create, injectionPoint, extData);
                DataAccessPluginExecutor.DefaultHandleActionResult(actionResult);
            }
            catch (Exception e)
            {
                throw new FasException(DataAccessErrorCode.PlugIn.Error, string.Format(CommonStringClassEngine.DataModelService_PluginExecFail, e.Message), e);
            }
        }

        private void InitDBEvent()
        {
            _db.BeforeSQLExecute += () => this.Execute(InjectionMethodType.Create, InjectionPoint.BeforeExecuteSQL);
            _db.ExecuteSQLSuccessed += result => this.Execute(InjectionMethodType.Create, InjectionPoint.ExecuteSQLSuccess);
        }

        private void RegistSQLExeSuccessdEvent(CompositeDataObject compositeDataObject)
        {
            var rootNode = compositeDataObject.RootCompositeNodeObject;
            _db.ExecuteSQLSuccessed += (result) =>
            {
                if (result.SQLExecuteContext.SqlBuildingInfo.CurrentNode.ID != rootNode.ID)  //只判断主节点数据影响行数
                    return;
                if (result.AffectedRows == 0)
                {
                    StringBuilder sb = new StringBuilder();
                    var paras = result.SQLExecuteContext.Parameters;
                    if (paras != null && paras.Count > 0)
                    {
                        foreach (var para in paras)
                        {
                            if (para == null)
                                continue;
                            sb.AppendLine($"ParaName:{para.ParameterName}, ParaValue:{para.Value?.ToString()}");
                        }
                    }
                    throw new FasException(DataAccessErrorCode.CompositeDataObject.AffectedRowsError, string.Format(CommonStringClassEngine.CDODataSaveService_RegistSQLExeSuccessdEvent,compositeDataObject?.Code));
                }
            };
        }
        #endregion

        #region Private Funcs
        /// <summary>
        /// 克隆TRecord
        /// </summary>
        /// <param name="metadata"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        private TRecord GetCreatedData(CompositeDataObject metadata, TRecord data)
        {
            //Clone或者别的方式获取拷贝, 防止AfterSave事件操作修改数据
            //Bug:TRecord.Clone()目前是浅拷贝，且Remove掉了主键
            var newData = data.CloneWithoutChangePkValue() as TRecord;
            //兼容处理，如果Copy时丢掉主键，则重新赋值
            newData.SetValue(data.PKColumnName, data.GetValue(data.PKColumnName));
            if (newData == null)
                throw new FasException(DataAccessErrorCode.CompositeDataObject.Error, string.Format(CommonStringClassEngine.CDODataSaveService_GetCreatedData,metadata.Code));
            return newData;
        }

        /// <summary>
        /// 取出TRecord中的主节点的主键值
        /// </summary>
        /// <param name="compositeDataObject"></param>
        /// <param name="record"></param>
        /// <returns></returns>
        private string GetCDOTRecordPrimaryKeyValue(CompositeDataObject compositeDataObject, TRecord record)
        {
            try
            {
                var pkCol = compositeDataObject.RootCompositeNodeObject?.DataObject.Columns.FirstOrDefault(c => c.IsPkColumn);
                return record.GetValue(pkCol.ColumnName).ToString();
            }
            catch (Exception e)
            {
                throw new Exception(string.Format(CommonStringClassEngine.DataModelService_GetRecordPkValueFail), e);
            }
        }

        /// <summary>
        /// 统一执行SQL，提供友好性报错提示
        /// </summary>
        /// <param name="metaData"></param>
        /// <param name="sqls"></param>
        /// <returns></returns>
        private void ExecuteSQL(CompositeDataObject metaData, SqlStatementCollection sqls, string billID = null)
        {
            try
            {
                _db.ExecuteSQLWithTransaction(sqls);
            }
            catch (Exception e)
            {
                throw new FasException(DataAccessErrorCode.CompositeDataObject.Error, string.Format(CommonStringClassEngine.CDODataSaveService_ExecuteSQL,metaData?.Code,billID), e);
            }
        }
        #endregion

    }
}
